/*  Copyright (C) 2008  Alexander Blyzniuchenko (BlezAlex)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace DC.Http
{
    class HttpListener
    {
        private static readonly Encoding _win1251Encoding = Encoding.GetEncoding(1251);
        private TcpListener _listener;
        private bool _isStarted = false;
        private const int READ_TIMEOUT = 15 * 1000; // 15 sec;
        private const int WRITE_TIMEOUT = 60 * 1000 * 5; // 5 min;
        private const int MAX_REQUEST_LEN = 1024;
        private const string EMPTY_LINE = "\r\n\r\n";

        public event EventHandler<ClientRecivedEventArgs> OnClientConnected;

        public void Start(IPAddress localaddress, int port)
        {
            if (_isStarted)
                throw new InvalidOperationException("Listener is already started");

            _listener = new TcpListener(localaddress, port);

            _isStarted = true;
            _listener.Start();
            StartAccept();
        }

        public void Stop()
        {
            _listener.Stop();
            _isStarted = false;
        }

        private void StartAccept()
        {
            if (_isStarted)
                _listener.BeginAcceptTcpClient(AcccetClientCallback, null);
        }

        private void AcccetClientCallback(IAsyncResult ar)
        {
            bool isAcceptRequested = false;
            try
            {
                // Retrieve client
                TcpClient client = _listener.EndAcceptTcpClient(ar);

                // Imidiatelly start new acccept
                isAcceptRequested = true;
                StartAccept();

                // Process client connection
                ProcessClient(client);
            }
            catch (ObjectDisposedException ex)
            {
                if (ex.ObjectName != "System.Net.Sockets.Socket")
                    throw;
            }
            catch (InvalidReqeustException ex)
            {
                Console.Error.WriteLine("InvalidReqeustException: {0}", ex.Message);
            }
            finally
            {
                if (!isAcceptRequested)
                    StartAccept();
            }
        }


        private void ProcessClient(TcpClient client)
        {
            using (NetworkStream stream = client.GetStream())
            {
                Request request;
                try
                {
                    stream.ReadTimeout = READ_TIMEOUT;
                    stream.WriteTimeout = WRITE_TIMEOUT;

                    StringBuilder requestString = new StringBuilder();
                    byte[] buffer = new byte[1024];
                    int bytesReaded;
                    int totalBytesReaded = 0;
                    do
                    {
                        bytesReaded = stream.Read(buffer, 0, buffer.Length);
                        string lastString = _win1251Encoding.GetString(buffer, 0, bytesReaded);
                        requestString.Append(lastString);
                        totalBytesReaded += bytesReaded;
                    } while (!requestString.ToString().Contains(EMPTY_LINE) && bytesReaded > 0 &&
                             totalBytesReaded < MAX_REQUEST_LEN);

                    Stream inputStream = stream;

                    int responseStartPos = requestString.ToString().IndexOf(EMPTY_LINE);
                    if (responseStartPos >= 0)
                    {
                        int copyPos = bytesReaded - (requestString.Length - responseStartPos - EMPTY_LINE.Length);
                        if (copyPos > 0 && bytesReaded > copyPos)
                        {
                            int copyLen = bytesReaded - copyPos;
                            byte[] unprocessedData = new byte[copyLen];
                            Array.Copy(buffer, copyPos, unprocessedData, 0, copyLen);
                            inputStream = new DCStream(stream, copyLen, unprocessedData);
                        }
                    }
                    Debug.WriteLine(requestString);
                    request = new Request(requestString.ToString(), inputStream, client.Client.RemoteEndPoint);

                }
                catch (IOException ex)
                {
                    stream.Close();
                    throw new InvalidReqeustException(ex);
                }

                Response response = new Response(stream);

                if (OnClientConnected != null)
                    OnClientConnected(this, new ClientRecivedEventArgs(request, response));

                response.Close();
                client.Close();
            }
        }
    }
}